package ace.cmp.spring.util.core;

import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.function.Consumer;
import java.util.function.Supplier;
import java.util.stream.Collectors;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.core.ResolvableType;

/**
 * @author caspar
 * @date 2023/9/20 9:23
 */
@Slf4j
public final class SpringUtils implements ApplicationContextAware {
  private ApplicationContext applicationContext = null;
  private DefaultListableBeanFactory defaultListableBeanFactory;

  @Override
  public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
    this.applicationContext = applicationContext;
    // 将applicationContext转换为ConfigurableApplicationContext
    ConfigurableApplicationContext configurableApplicationContext =
        (ConfigurableApplicationContext) applicationContext;
    // 获取bean工厂并转换为DefaultListableBeanFactory
    this.defaultListableBeanFactory =
        (DefaultListableBeanFactory) configurableApplicationContext.getBeanFactory();
    log.info("init ApplicationContext and  BeanFactory Success.");
  }

  public DefaultListableBeanFactory getDefaultListableBeanFactory() {
    return defaultListableBeanFactory;
  }

  public ApplicationContext getApplicationContext() {
    return applicationContext;
  }

  public <T> T getBeanByClassWithGenerics(Class clazz, Class... args) {
    ResolvableType resolvableType = ResolvableType.forClassWithGenerics(clazz, args);
    ObjectProvider<T> objectProvider =
        this.getDefaultListableBeanFactory().getBeanProvider(resolvableType);
    T bean = objectProvider.getIfUnique();
    return bean;
  }

  public <T> ObjectProvider<T> getBeanByClassWithGenericsObjectProvider(
      Class clazz, Class... args) {
    ResolvableType resolvableType = ResolvableType.forClassWithGenerics(clazz, args);
    ObjectProvider<T> objectProvider =
        this.getDefaultListableBeanFactory().getBeanProvider(resolvableType);
    return objectProvider;
  }

  public <T> T getOrRegisterSingletonBean(T bean) {
    Class<T> clazz = (Class<T>) bean.getClass();
    String beanName = clazz.getSimpleName();

    // 检查是否存在重名的bean，如果存在打印警告日志，并且返回，
    if (getDefaultListableBeanFactory().containsBean(beanName)) {
      log.info(
          "The Bean  [{}] for  type [{}] is already exists. Please check.",
          beanName,
          clazz.getName());
      return getApplicationContext().getBean(clazz);
    }

    // 注册bean
    defaultListableBeanFactory.registerSingleton(beanName, bean);
    log.info("register bean [{}],Class [{}] success.", beanName, clazz);
    return (T) getDefaultListableBeanFactory().getBean(bean.getClass());
  }

  /**
   * 注册bean到Spring容器。使用构造函数参数初始化bean。
   * 备注：需要有默认构造器，即需要有无参构造器。
   *
   * @param beanName
   * @param clazz
   * @param constructorArgs
   */
  public void registerBean(String beanName, Class<?> clazz, Object... constructorArgs) {
    registerBean(
        beanName,
        clazz,
        (builder) -> {
          log.info("使用构造函数参数初始 beanName[{}],class[{}]", beanName, clazz);
          if (constructorArgs != null && constructorArgs.length > 0) {
            for (Object constructorArg : constructorArgs) {
              builder.addConstructorArgValue(constructorArg);
            }
          }
        });
  }

  /**
   * 注册bean到spring容器中。使用属性参数初始化bean。
   *
   * @param beanName 名称
   * @param clazz    class
   */
  public void registerBean(String beanName, Class<?> clazz, Map<String, Object> propertyValueMap) {
    registerBean(
        beanName,
        clazz,
        (beanDefinitionBuilder) -> {
          log.info("使用属性参数初始化 beanName[{}],class[{}]", beanName, clazz);
          if (propertyValueMap != null && propertyValueMap.size() > 0) {
            propertyValueMap.forEach(
                (k, v) -> {
                  beanDefinitionBuilder.addPropertyValue(k, v);
                });
          }
        });
  }

  public void registerBean(
      String beanName, Class<?> clazz, Consumer<BeanDefinitionBuilder> builderConsumer) {
    // 1. 检查是否存在重名的bean，如果存在打印警告日志，并且返回，
    if (defaultListableBeanFactory.containsBean(beanName)) {
      log.warn(
          "The Bean  [{}] for  type [{}] is already exists. Please check.",
          beanName,
          clazz.getName());
      return;
    }
    // 2. 通过BeanDefinitionBuilder创建bean定义
    BeanDefinitionBuilder beanDefinitionBuilder =
        BeanDefinitionBuilder.genericBeanDefinition(clazz);

    // 3. 初始化Bean
    if (builderConsumer != null) {
      builderConsumer.accept(beanDefinitionBuilder);
    }

    // 4. 注册bean
    defaultListableBeanFactory.registerBeanDefinition(
        beanName, beanDefinitionBuilder.getRawBeanDefinition());
    log.info("register bean [{}],Class [{}] success.", beanName, clazz);
  }

  public <T> void registerRootBean(
      String beanName,
      ResolvableType resolvableType,
      Supplier<T> instanceSupplier,
      Consumer<BeanDefinitionBuilder> builderConsumer) {
    Class clazz = resolvableType.getRawClass();
    // 1. 检查是否存在重名的bean，如果存在打印警告日志，并且返回，
    if (defaultListableBeanFactory.containsBean(beanName)) {
      log.warn("The Bean  [{}] for  type [{}] is already exists. Please check.", beanName, clazz);
      return;
    }
    // 2. 通过BeanDefinitionBuilder创建bean定义
    BeanDefinitionBuilder beanDefinitionBuilder =
        BeanDefinitionBuilder.rootBeanDefinition(resolvableType, instanceSupplier);

    // 3. 初始化Bean
    if (builderConsumer != null) {
      builderConsumer.accept(beanDefinitionBuilder);
    }

    // 4. 注册bean
    defaultListableBeanFactory.registerBeanDefinition(
        beanName, beanDefinitionBuilder.getRawBeanDefinition());
    log.info("register bean [{}],Class [{}] success.", beanName, clazz);
  }

  @SneakyThrows
  public Boolean containsBean(ResolvableType resolvableType) {
    List<BeanDefinition> beanDefinitions =
        Arrays.stream(this.getDefaultListableBeanFactory().getBeanDefinitionNames())
            .map(beanName -> this.getDefaultListableBeanFactory().getBeanDefinition(beanName))
            .filter(
                beanDefinition -> {
                  if (beanDefinition.getBeanClassName() == null) {
                    return false;
                  }
                  try {
                    ResolvableType beanDefinitionResolvableType =
                        ResolvableType.forClass(Class.forName(beanDefinition.getBeanClassName()));
                    Boolean isMatch =
                        resolvableType
                            .getRawClass()
                            .isAssignableFrom(beanDefinitionResolvableType.getRawClass());
                    return isMatch;
                  } catch (ClassNotFoundException e) {
                    throw new RuntimeException(e);
                  }
                })
            .collect(Collectors.toList());

    return beanDefinitions.size() > 0;
  }
}
